﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using Microsoft.Win32;
using System.Runtime.InteropServices;

namespace NoAPM
{
    public partial class Form1 : Form
    {
        [DllImport("user32.dll")] private static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);

        private List<String> drives = new List<String>();
        private List<String> drivedescs = new List<String>();

        private bool starthidden = false;

        private const string smartcmd = "\\smartmontools\\bin\\smartctl.exe";
        String path = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
        private simpleConfig cfg = new simpleConfig(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\NoAPM", "noapm.cfg");

        #region Form related functions
        public Form1(bool hidden)
        {
            starthidden = hidden;
            InitializeComponent();

            if (cfg.autorun > 0)
            {
                cbAutostart.Checked = true;
                checkregistryautorun();
            }
            if (cfg.autoexit > 0)
                cbExitAfterDisable.Checked = true;

            if (starthidden)
            {
                ShowWindowAsync((IntPtr)this.Handle, 0);
            }
            else
            {
                ShowWindowAsync((IntPtr)this.Handle, 1);
                this.Opacity = 100;
                this.ShowInTaskbar = true;
            }

            SystemEvents.PowerModeChanged += OnPowerChange;
        }

        private void Form1_Load(object sender, EventArgs e)
        {

            timer1.Enabled = true;

        }

        private void tbLog_VisibleChanged(object sender, EventArgs e)
        {
            if (tbLog.Visible)
            {
                tbLog.SelectionStart = tbLog.TextLength;
                tbLog.ScrollToCaret();
            }
        }

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            notifyIcon1.Visible = false;
        }

        private void notifyIcon1_MouseDoubleClick(object sender, MouseEventArgs e)
        {
            this.Opacity = 100;
            this.ShowInTaskbar = true;
            ShowWindowAsync((IntPtr)this.Handle, 1);
        }

        private void Form1_Resize(object sender, EventArgs e)
        {
            if (WindowState == FormWindowState.Minimized)
            {
                this.Opacity = 0;
                this.ShowInTaskbar = false;
                ShowWindowAsync((IntPtr)this.Handle, 0);
            }
        }

        private void cbExitAfterDisable_Click(object sender, EventArgs e)
        {
            if (cbExitAfterDisable.Checked)
                cfg.autoexit = 1;
            else
                cfg.autoexit = 0;
            cfg.SaveConfig();
        }

        private void cbAutostart_Click(object sender, EventArgs e)
        {
            if (cbAutostart.Checked)
                cfg.autorun = 1;
            else
                cfg.autorun = 0;
            cfg.SaveConfig();
            checkregistryautorun();
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            timer1.Enabled = false;
            UpdateDrivesList();
            DisableAPM();
            if (this.cfg.autoexit > 0 && this.starthidden == true)
            {
                notifyIcon1.Visible = false;
                Environment.Exit(0);
            }
        }

        private void OnPowerChange(object s, PowerModeChangedEventArgs e)
        {
            switch (e.Mode)
            {
                case PowerModes.Resume:
                    tbLog.AppendText("WakeUp detected" + Environment.NewLine);
                    DisableAPM();
                    break;
                case PowerModes.Suspend:
                    break;
            }
        }

        #endregion

        #region Utility functions
        private int DisableAPM()
        {
            int disablecount = 0;

            for (int i = 0; i < drives.Count; i++)
            {
                tbLog.AppendText(("Disabling APM on drive: " + drivedescs[i] + " " + drives[i]) + Environment.NewLine);
                if (RunAndGetStdout(path + smartcmd, "-s apm,off " + drives[i]).ToLower().Contains("apm disabled"))
                {
                    disablecount++;
                    tbLog.AppendText("Successfully disabled APM on drive " + drivedescs[i] + " " + drives[i] + Environment.NewLine);
                }
                else
                    tbLog.AppendText("Failed to disable APM on drive " + drivedescs[i] + " " + drives[i] + Environment.NewLine);
            }
            tbLog.AppendText(Environment.NewLine);
            return disablecount;
        }

        private void UpdateDrivesList()
        {
            tbLog.AppendText("Updating Drives List..." + Environment.NewLine);
            String drivesList = "";

            drives.Clear();
            drivedescs.Clear();

            try
            {
                drivesList = RunAndGetStdout(path + smartcmd, "--scan");
            }
            catch (Exception exc)
            {
                //todo: handle errors
                tbLog.AppendText("Error Getting Drives List... is smartmontools missing?" + Environment.NewLine);
                MessageBox.Show("NoAPM: Error Getting Drives List... is smartmontools missing?","NoAPM",MessageBoxButtons.OK, MessageBoxIcon.Error);
            }

            foreach (String line in drivesList.Split(Environment.NewLine.ToCharArray(), StringSplitOptions.RemoveEmptyEntries))
            {
                if (line.ToLower().StartsWith("/dev/"))
                    drives.Add((line.Split(' '))[0]);
            }

            for (int i = drives.Count - 1; i >= 0; i--)
            {
                String tempapm = RunAndGetStdout(path + smartcmd, "-g apm " + drives[i]).ToLower();
                if (!tempapm.Contains("apm level is") && (tempapm.Contains("unavailable") || !tempapm.Contains("apm feature")))
                {
                    drives.RemoveAt(i);
                }
            }


            if (drives.Count == 0)
                tbLog.AppendText("No APM compatible drives found.");

            int j;
            for (int i = drives.Count -1 ; i >=0 ; i--)
            {
                String tempnameraw = RunAndGetStdout(path + smartcmd, "-i " + drives[i]);
                String tempname = "";
                foreach (String line in tempnameraw.Split(Environment.NewLine.ToCharArray(), StringSplitOptions.RemoveEmptyEntries))
                {
                    if (line.Contains("Device Model"))
                    {
                        int startindex = line.LastIndexOf(' ') + 1;
                        tempname = (line.Substring(startindex, line.Length - startindex));
                    }
                    else if (line.Contains("Serial Number"))
                    {
                        int startindex = line.LastIndexOf(' ') + 1;
                        tempname += " (" + (line.Substring(startindex, line.Length - startindex)) + ")";
                    }
                }
                //let's make sure there are no duplicates due to the /dev/sdx /dev/csmix,x thing
                for (j = 0; j < drivedescs.Count; j++)
                {
                    if (drivedescs[j] == tempname)
                    {
                        drives.RemoveAt(i);
                        break;
                    }
                }
                if (j == drivedescs.Count)
                {
                    drivedescs.Add(tempname);
                    tbLog.AppendText("Found drive with APM: " + tempname + Environment.NewLine);
                }
            }

            tbLog.AppendText(Environment.NewLine);
        }


        private static String RunAndGetStdout(String path, String args)
        {
            StringBuilder outputBuilder = new StringBuilder();
            System.Diagnostics.ProcessStartInfo processStartInfo = new System.Diagnostics.ProcessStartInfo();
            processStartInfo.CreateNoWindow = true;
            processStartInfo.RedirectStandardOutput = true;
            processStartInfo.RedirectStandardInput = true;
            processStartInfo.UseShellExecute = false;
            processStartInfo.Arguments = args;
            processStartInfo.FileName = path;

            System.Diagnostics.Process process = new System.Diagnostics.Process();
            process.StartInfo = processStartInfo;
            process.EnableRaisingEvents = true;
            process.OutputDataReceived += new System.Diagnostics.DataReceivedEventHandler
            (
                delegate(object sender, System.Diagnostics.DataReceivedEventArgs e)
                {
                    outputBuilder.Append(e.Data + Environment.NewLine);
                }
            );

            process.Start();
            process.BeginOutputReadLine();
            process.WaitForExit();
            process.CancelOutputRead();

            return outputBuilder.ToString();
        }
        
        private void checkregistryautorun()
        {
            RegistryKey key = Registry.CurrentUser.CreateSubKey("Software\\Microsoft\\Windows\\CurrentVersion\\Run");
            if (this.cfg.autorun > 0 && (key.GetValue("noapm_autorun") == null || key.GetValue("noapm_autorun") != Application.ExecutablePath + " /startup"))
            {
                key.SetValue("noapm_autorun", Application.ExecutablePath + " /startup");
            }
            else if (this.cfg.autorun == 0 && key.GetValue("noapm_autorun") != null)
                key.DeleteValue("noapm_autorun");
        }

        #endregion

    }

    #region Config class
    public class simpleConfig
    {
        private String filepath;
        private List<String> cfgdata;

        public int autorun = 0;
        public int autoexit = 0;

        public simpleConfig(String directory, String filename)
        {
            filepath = directory + "\\" + filename;
            if (!System.IO.File.Exists(this.filepath))
            {
                if (!System.IO.Directory.Exists(directory))
                {
                    System.IO.Directory.CreateDirectory(directory);
                }

                System.IO.FileStream fs = System.IO.File.Create(this.filepath);
                fs.Close();
            }
            System.IO.StreamReader sr = new System.IO.StreamReader(this.filepath);
            cfgdata = new List<String>();
            while (!sr.EndOfStream)
            {
                cfgdata.Add(sr.ReadLine());
            }
            sr.Close();

            autorun = strtoint(getValueAndRemove("autorun"), 0);
            autoexit = strtoint(getValueAndRemove("autoexit"), 0);
        }

        public void SaveConfig()
        {
            String newcfg = "";
            newcfg += "autorun=" + this.autorun + Environment.NewLine;
            newcfg += "autoexit=" + this.autoexit + Environment.NewLine;
            System.IO.StreamWriter sw = new System.IO.StreamWriter(this.filepath);
            sw.Write(newcfg);
            sw.Close();
        }

        private String getValueAndRemove(String name)
        {
            String retval = "";

            for (int i = 0; i < cfgdata.Count; i++)
            {
                if (cfgdata[i].StartsWith(name + "="))
                {
                    retval = (cfgdata[i].Split('='))[1];
                    cfgdata.RemoveAt(i);
                    break;
                }
            }

            return retval;
        }

        private static Int32 strtoint(String text, Int32 defaultvalue)
        {
            Int32 retval;
            try
            {
                retval = Int32.Parse(text);
            }
            catch (Exception exc)
            {
                retval = defaultvalue;
            }
            return retval;
        }

    }
    #endregion
}
